package com.syun.spring.ioc.core.factory;

import com.syun.spring.ioc.core.bean.BeanDefinition;
import com.syun.spring.ioc.core.bean.Property;
import com.syun.spring.ioc.core.bean.BeanUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.exception.ExceptionUtils;

/**
 * 读取xml配置文件
 */
public class ClassPathXmlApplicationContext implements ApplicationContext {

    private String configLocation;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>() ;

    private Map<String, Object> iocMap = new HashMap<String, Object>();


    public ClassPathXmlApplicationContext(String configLocation){
        this.configLocation = configLocation;
        parseXmlFile();
        if(!beanDefinitionMap.isEmpty()){
            for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()){
                String id = entry.getKey();
                //在依赖注入的时候，部分bean可能会提前已创建
                if(!iocMap.containsKey(id)){
                    BeanDefinition bean = entry.getValue();
                    createBean(bean);
                }
            }
        }
    }

    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(null == beanDefinition){
            throw new RuntimeException("no beanDefinition : " + beanName);
        }
        Object object = iocMap.get(beanName);
        if(null != object){
            return object;
        } else {
            return createBean(beanDefinition);
        }
    }

    public Object createBean(BeanDefinition bean){
        Object object = null;
        Class cls = null;
        String beanName = bean.getBeanName();
        String className = bean.getClassName();
        List<Property> properties = bean.getProperties();
        System.out.println("["+beanName+"] create start>>>>");
        //反射生成Class对象
        try {
            cls = Class.forName(className);
            System.out.println("["+beanName+"] class >> :" + cls.toString());
            bean.setBeanClass(cls);
        } catch (Exception e) {
            throw new RuntimeException("reflex class fail:["+beanName+"]" + "\n" +ExceptionUtils.getStackTrace(e));
        }
        try {
            object = cls.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("class instance fail:["+beanName+"]" + "\n" +ExceptionUtils.getStackTrace(e));
        }
        //设置属性
        if(!properties.isEmpty()){
            for(Property property:properties){
                //value注入
                if(property.getValue() != null){
                    Method setterMethod = BeanUtils.getSetterMethod(object, property.getName());
                    try{
                        setterMethod.invoke(object, property.getValue());
                        System.out.println("["+beanName+"] set value >> " + property.getName() + ":" + property.getValue());
                    }catch (Exception e){
                        throw new RuntimeException("set value fail:["+beanName+"."+property.getName() +"]" + "\n" +ExceptionUtils.getStackTrace(e));
                    }
                }
                //依赖注入
                if(property.getRef() != null){
                    Method setterMethod = BeanUtils.getSetterMethod(object, property.getName());
                    Object refObj = getBean(property.getRef());
                    if(refObj == null){
                        throw new RuntimeException("set ref fail:["+property.getRef()+"] is null");
                    }
                    try{
                        setterMethod.invoke(object, refObj);
                        System.out.println("["+beanName+"] set DI >> " + property.getName() + ":" + refObj.toString());
                    }catch (Exception e){
                        throw new RuntimeException("set ref fail:["+beanName+"."+property.getRef() +"] " + "\n" +ExceptionUtils.getStackTrace(e));
                    }
                }
            }
        }
        System.out.println("["+beanName+"] end>> :" + object.toString());
        iocMap.put(beanName, object);
        return object;
    }

    /**
     * 解析xml文件
     * @return
     */
    private void parseXmlFile(){
        Document document = null;
        SAXReader reader = new SAXReader();
        InputStream inputStream = ClassPathXmlApplicationContext.class.getResourceAsStream(configLocation);
        try {
            document = reader.read(inputStream);
        } catch (DocumentException e) {
            throw new RuntimeException("read xml file fail:" + "\n" +ExceptionUtils.getStackTrace(e));
        }
        List<Element> elementList = document.selectNodes("//bean");
        if(!elementList.isEmpty()){
            // 遍历所有bean节点
            for(Element element : elementList){
                String id = element.attributeValue("id");
                if(beanDefinitionMap.containsKey(id)){
                    throw new RuntimeException("bean already exists: " + id);
                }
                String className = element.attributeValue("class");
                BeanDefinition bean = new BeanDefinition(id, className);
                //遍历bean下所有property属性节点
                List<Element> properties= element.elements("property");
                if(!properties.isEmpty()){
                    for(Element pElement : properties){
                        String name  = pElement.attributeValue("name");
                        String value = pElement.attributeValue("value");
                        String ref = pElement.attributeValue("ref");
                        Property property = new Property(name, value, ref);
                        bean.getProperties().add(property);
                    }
                }
                beanDefinitionMap.put(id, bean);
            }
        }
    }
}
